/*
 *  Copright: 2008 Philipp Leitner, Distributed Systems Group,
 *            TU Wien, leitner@infosys.tuwien.ac.at
 *
 *  This file is part of DAIOS.
 *
 *  DAIOS is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation.
 * 
 *  DAIOS is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with DAIOS.  If not, see
 *  <http://www.gnu.org/licenses/>.
 * 
 */

package at.ac.tuwien.infosys.dsg.daios.wsdl.matcher;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import at.ac.tuwien.infosys.dsg.daios.exceptions.AmbigiousException;
import at.ac.tuwien.infosys.dsg.daios.exceptions.NoArrayException;
import at.ac.tuwien.infosys.dsg.daios.exceptions.TypeErrorException;
import at.ac.tuwien.infosys.dsg.daios.framework.structs.DaiosInputMessage;
import at.ac.tuwien.infosys.dsg.daios.framework.structs.DaiosMessage;
import at.ac.tuwien.infosys.dsg.daios.wsdl.AtomicTypesMapper;
import at.ac.tuwien.infosys.dsg.daios.wsdl.impl.WSDLArrayType;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLType;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLMessage;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLOperation;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLPart;
import at.ac.tuwien.infosys.dsg.daios.wsdl.typesystem.WSDLTypeTree;

public class RPCMatcher extends Matcher {
	
	/**
	 * Calculate the distance between a single WSDL message and a
	 * Daios input message.
	 * 
	 * @param input The Daios input message.
	 * @param m The WSDL message to compare to the Daios message.
	 * @param ops A list of all operations to consider.
	 * @param types A type tree containing all types from the WSDL definition.
	 * @return The distance between the Daios message and the WSDL message as Integer.
	 * 
	 * @throws AmbigiousException
	 */
	protected int distance(DaiosInputMessage input, IWSDLMessage m,
			List<IWSDLOperation> ops, WSDLTypeTree types) {
		
		// check if this is an input message
		if(!isInputMessage(m, ops))
			return MAX_DISTANCE;
		
		// else check structural proximity
		
		// the idea here is the following:
		// we get a list of all WSDL parts and types,
		// then we remove all parts which are also in the
		// Daios message. If we find a Daios field which
		// is not represented by a part we return MAX.
		// If all Daios fields are removed we return
		// the number of remaining fields as distance.
		
		List<IWSDLPart> parts = m.getParts();
		Hashtable<String,String> partsList = new Hashtable<String,String>();
		for(IWSDLPart p:parts) {
			partsList.put(p.getPartName(),p.getPartType());
		}
		
		Enumeration<String> names = input.getNames();
		String n = null;
		Class t = null;
		while(names.hasMoreElements()) {
			n = names.nextElement();
			t = input.get(n).getClass();
			
			// value does not exist
			if(partsList.get(n) == null)
				return MAX_DISTANCE;
			
			boolean isSimple = input.isSimple(n);
			boolean isArray = input.isArray(n);
			
			// simple type
			if(isSimple && !isArray) {
				
				if(!simpleMatch(partsList.get(n), t))
					return MAX_DISTANCE;
			
			}
			
			// array type
			else if(isSimple && isArray) {
				
				try {
					if(!arrayMatch(partsList.get(n), input.get(n).getClass(),
							types)) {
						return MAX_DISTANCE;
					}
				} catch (NoArrayException e) {
					e.printStackTrace();
					return MAX_DISTANCE;
				}
				
			}
			
			// complex type
			else if(!isSimple && !isArray) {
				try {
					if(!complexMatch(partsList.get(n), input.getComplex(n), types))
						return MAX_DISTANCE;
				} catch (TypeErrorException e) {
					e.printStackTrace();
					return MAX_DISTANCE;
				}
			}
			
			// array of complex values
			else {
				try {
					if(!complexArrayMatch(partsList.get(n), input.getComplexArray(n), types))
						return MAX_DISTANCE;
				} catch (TypeErrorException e) {
					e.printStackTrace();
					return MAX_DISTANCE;
				}
			}
					
			partsList.remove(n);
		}
		
		return partsList.size();
	}

	/**
	 * Check if this Daios simple type matches a 
	 * given WSDL type.
	 * 
	 * @param wsdlType The WSDL type.
	 * @param t The Daios simple type.
	 * @return True if the WSDL and the Daios type match,
	 * false otherwise.
	 */
	private boolean simpleMatch(String wsdlType, Class t) {
		
		return wsdlType.equals(AtomicTypesMapper.get(t));
		
	}

	/**
	 * Check if this Daios array type matches a 
	 * given WSDL type.
	 * 
	 * @param wsdlType The WSDL type.
	 * @param arrayType The Daios array type.
	 * @param types The type tree.
	 * @return True if the WSDL and the Daios types match,
	 * false otherwise.
	 * 
	 * @throws NoArrayException
	 */
	private boolean arrayMatch(String wsdlType,
		Class arrayType, WSDLTypeTree types) throws NoArrayException {
		
		// check if the object given is actually an array
		if(!arrayType.isArray())
			throw new NoArrayException(arrayType.getClass());
		
		IWSDLType cType = types.getGlobalComplexType(wsdlType);
		
		if(!(cType instanceof WSDLArrayType))
			return false;
		
		WSDLArrayType arr = (WSDLArrayType) cType;
		
		return arrayType.equals(
			AtomicTypesMapper.getArray(arr.getArrayType()));
		
	}
	
	/**
	 * Check if this Daios complex type matches a 
	 * given WSDL type.
	 * 
	 * @param name The WSDL type.
	 * @param complexType The Daios complex type.
	 * @param types The type tree.
	 * @return True if the WSDL and the Daios types match,
	 * false otherwise.
	 */	
	private boolean complexMatch(String name, DaiosMessage complexType,
			WSDLTypeTree types) {
		
		IWSDLType type = types.getGlobalComplexType(name);
		
		if(type == null)
			return false;
			
		int distance = type.calculateDistanceToMessage(complexType);
			
		return  distance == 0;

	}

	/**
	 * Check if this array of Daios complex types matches a 
	 * given WSDL type.
	 * 
	 * @param name The WSDL type.
	 * @param complexArray The Daios complex type array.
	 * @param types The type tree.
	 * @return True if the WSDL and the Daios array match,
	 * false otherwise.
	 */	
	private boolean complexArrayMatch(String name, DaiosMessage[] complexArray,
			WSDLTypeTree types) {
		
		IWSDLType type = types.getGlobalComplexType(name);
		
		if(!(type instanceof WSDLArrayType))
			return false;
		
		WSDLArrayType arr = (WSDLArrayType) type;
		
		
		// for simplicity we only check whether the first message matches
		// the requested type - this silently assumes that all following
		// messages have the same structure (what should be ensured by
		// the Daios framework anyway)
		return complexMatch(arr.getArrayType(), complexArray[0], types);
		
	}
	
}
